/**
 * Copyright(c) 2013 Jade Techonologies Co., Ltd.
 *
 * History:
 *   13-9-7 下午9:05 Created by dwzhang
 */
package com.jade.framework.base.text;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.jade.framework.base.JadeSystem;
import org.apache.commons.lang3.StringUtils;

/**
 * 变量工具。
 *
 * @author dwzhang
 * @version 1.0 13-9-7 下午9:05
 */
public class VariableUtils
{
    private VariableUtils()
    {
    }

    /**
     * Replaces <code>${xxx}</code> style constructions in the given value
     * with the string value of the corresponding data types.
     * 支持${XXX}方式获取变量。
     *
     * @param value      The string to be scanned for property references.
     *                   May be <code>null</code>, in which case this
     *                   method returns immediately with no effect.
     * @param parameters Mapping (String to String) of property names to their
     *                   values. Must not be <code>null</code>.
     * @return the original string with the properties replaced, or
     *         <code>null</code> if the original string is <code>null</code>.
     */
    public static String evaluate(String value, Map<String, String> parameters)
    {
        return evaluate(null, value, parameters);
    }

    public static String evaluate(String namespace, String value, Map<String, String> parameters)
    {
        if (value == null) {
            return null;
        }
        else if (!value.contains("${")) {
            //处理${XXX}的情况以获取环境变量
            return parseValue(value);
        }

        List fragments = new ArrayList();
        List propertyRefs = new ArrayList();
        parseValue(value, fragments, propertyRefs);

        StringBuilder sb = new StringBuilder();
        Iterator refs = propertyRefs.iterator();
        for (int i = 0, size = fragments.size(); i < size; i++) {
            String fragment = (String)fragments.get(i);
            if (fragment == null) {
                String propertyName = (String)refs.next();
                String propertyValue = null;
                if (StringUtils.isNotBlank(namespace)) {
                    if (!propertyName.startsWith(namespace)) {
                        propertyValue = getProperty(namespace + "." + propertyName, parameters);
                    }
                }
                if (propertyValue == null) {
                    propertyValue = getProperty(propertyName, parameters);
                }
                if (propertyValue == null) {
                    fragment = "${" + propertyName + "}";
                }
                else {
                    fragment = propertyValue;
                }
            }
            sb.append(fragment);
        }

        return sb.toString();
    }

    protected static String getProperty(String key, Map<String, String> parameters)
    {
        if (parameters == null) {
            return JadeSystem.getProperty(key);
        }
        else {
            return parameters.get(key);
        }
    }

    /**
     * ${XXX}来获取环境变量，如果没有对应的值，则返回原值
     *
     * @param value 需要解析的值
     * @return 解析后的值
     */
    private static String parseValue(String value)
    {
        if (value.indexOf('$') == 0) {
            String envValue = System.getenv(value.substring(1));
            if (envValue != null) {
                return envValue;
            }
        }
        return value;
    }

    /**
     * Parses a string containing <code>${xxx}</code> style property
     * references into two lists. The first list is a collection
     * of text fragments, while the other is a set of string property names.
     * <code>null</code> entries in the first list indicate a property
     * reference from the second list.
     *
     * @param value        Text to parse. Must not be <code>null</code>.
     * @param fragments    List to add text fragments to.
     *                     Must not be <code>null</code>.
     * @param propertyRefs List to add property names to.
     *                     Must not be <code>null</code>.
     */
    private static void parseValue(String value, List<String> fragments, List<String> propertyRefs)
    {
        int prev = 0;
        int pos;
        //search for the next instance of $ from the 'prev' position
        while ((pos = value.indexOf('$', prev)) >= 0) {

            //if there was any text before this, add it as a fragment
            //seems like this current version could stick empty strings
            //into the list
            if (pos > 0) {
                fragments.add(value.substring(prev, pos));
            }
            //if we are at the end of the string, we tack on a $
            //then move past it
            if (pos == (value.length() - 1)) {
                fragments.add("$");
                prev = pos + 1;
            }
            else if (value.charAt(pos + 1) != '{') {
                //peek ahead to see if the next char is a property or not
                //not a property: insert the char as a literal
                if (value.charAt(pos + 1) == '$') {
                    //backwards compatibility two $ map to one mode
                    fragments.add("$");
                    prev = pos + 2;
                }
                else {
                    //new behaviour: $X maps to $X for all values of X!='$'
                    fragments.add(value.substring(pos, pos + 2));
                    prev = pos + 2;
                }

            }
            else {
                //property found, extract its name or bail on a typo
                int endName = value.indexOf('}', pos);
                if (endName < 0) {
                    throw new IllegalArgumentException("Syntax error in property: " + value);
                }
                String propertyName = value.substring(pos + 2, endName);
                fragments.add(null);
                propertyRefs.add(propertyName);
                prev = endName + 1;
            }
        }
        //no more $ signs found
        //if there is any tail to the file, append it
        if (prev < value.length()) {
            fragments.add(value.substring(prev));
        }
    }
}
